package com.tywj.lib.core.common.util

import java.lang.Boolean
import java.lang.Byte
import java.lang.Double
import java.lang.Float
import java.lang.Long
import java.lang.Short
import java.lang.reflect.Field
import java.lang.reflect.Method
import java.lang.reflect.Modifier

/**
 * <pre>
 *     author : June Yang
 *     time   : 2020/5/7
 *     desc   : 访问私有字段工具类
 *     version: 1.0.0
 * </pre>
 */
object PrivateUtil {

    @Suppress("UNCHECKED_CAST")
    fun <T> getFiled(`object`: Any, filedName: String): T {
        return findFiled(
            `object`,
            filedName
        ).get(`object`) as T
    }

    fun setFiled(`object`: Any, filedName: String, filedValue: Any) {
        findFiled(`object`, filedName)
            .set(`object`, filedValue)
    }

    fun getMethod(`object`: Any, methodName: String, vararg parameterTypes: Class<*>): Method {
        return findMethod(
            false,
            `object`,
            methodName,
            *parameterTypes
        )
    }

    /**
     * 使用反射调用指定方法
     * 注意：如果方法有重载且区别仅为一个是基础数据类型和另一个是对应的封装类型，请使用 [getMethod] 获取方法，然后调用该方法
     *      例如：需要调用的方法形如 setPerson(String name, int age) 和 setAge(String name, Integer age)，
     *           由于Java的自动装箱与拆箱机制，可能不能正确的调用到指定的方法
     */
    @Suppress("UNCHECKED_CAST")
    fun <T> invokeMethod(`object`: Any, methodName: String, vararg parameters: Any): T {
        val method = findMethod(
            true,
            `object`,
            methodName,
            *(parameters.map { it.javaClass }.toTypedArray())
        )
        return method.invoke(`object`, *parameters) as T
    }

    private fun findFiled(`object`: Any, filedName: String): Field {
        var clazz: Class<*> = `object` as? Class<*> ?: `object`.javaClass
        while (true) {
            val fields = clazz.declaredFields
            for (field in fields) {
                if (field.name == filedName) {
                    field.isAccessible = true
                    val modifiersField = Field::class.java.getDeclaredField("modifiers")
                    modifiersField.isAccessible = true
                    modifiersField.setInt(field, field.modifiers and Modifier.FINAL.inv())
                    return field
                }
            }

            clazz = clazz.superclass
            if (clazz == Any::class.java) {
                throw NoSuchFieldException("filed $filedName not exit in ${`object`.javaClass}")
            }
        }
    }

    private fun findMethod(
        boxIfPrimitive: kotlin.Boolean,
        `object`: Any,
        methodName: String,
        vararg parameterTypes: Class<*>
    ): Method {
        var clazz: Class<*> = `object` as? Class<*> ?: `object`.javaClass
        while (true) {
            val methods = clazz.declaredMethods
            for (method in methods) {
                if (method.name == methodName) {
                    if (method.parameterTypes.size == parameterTypes.size) {
                        var isMatch = true
                        method.parameterTypes.forEachIndexed { index, parameterClazz ->
                            val parameterC = if (boxIfPrimitive) boxIfPrimitive(
                                parameterClazz
                            ) else parameterClazz

                            if (parameterC != parameterTypes[index]) {
                                isMatch = false
                                return@forEachIndexed
                            }
                        }

                        if (isMatch) {
                            method.isAccessible = true
                            return method
                        }
                    }
                }
            }

            clazz = clazz.superclass
            if (clazz == Any::class.java) {
                val sb = StringBuilder()
                sb.append("method $methodName")
                parameterTypes.forEachIndexed { index, c ->
                    when (index) {
                        0 -> sb.append("(").append(c.toString())
                        parameterTypes.lastIndex -> sb.append(c.toString()).append(")")
                        else -> sb.append(c.toString()).append(", ")
                    }
                }
                sb.append(" not exit in ${`object`.javaClass}")
                throw NoSuchMethodException(sb.toString())
            }
        }
    }

    private fun boxIfPrimitive(type: Class<*>): Class<*> {
        return when (type.name) {
            "boolean" -> Boolean::class.java
            "char" -> Character::class.java
            "byte" -> Byte::class.java
            "short" -> Short::class.java
            "int" -> Integer::class.java
            "float" -> Float::class.java
            "long" -> Long::class.java
            "double" -> Double::class.java
            else -> type
        }
    }
}